package com.tachoknight.geocaching;

import java.math.BigDecimal;
import java.util.List;

public class Waypoint
{
	/**
	 * 
	 * Converts between coordinates in decimal notation and degrees/minutes My
	 * first crack at this, so it's probably not 100%, but my initial tests seem
	 * to think so, so I'll run with it (for now).
	 * 
	 */
	private class MinutesCoordinates
	{
		private int degrees;
		private int minutes;

		private double getDecimalPart(Double number)
		{
			double retVal = number - number.intValue();
			BigDecimal bd = new BigDecimal(retVal);
			bd = bd.setScale(2, BigDecimal.ROUND_HALF_EVEN);
			return Math.abs(bd.doubleValue());
		}

		public MinutesCoordinates(Double decimalDegrees)
		{
			degrees = decimalDegrees.intValue();
			minutes = (int) (getDecimalPart(decimalDegrees) * 100.0);
		}

		public int getDegrees()
		{
			return degrees;
		}

		public int getMinutes()
		{
			return minutes;
		}
	}

	
	private String name;
	private String description;
	private double latitude;
	private double longitude;

	/**
	 * 
	 * @param latitude
	 *            (in Decimal degrees e.g. 41.89637)
	 * @param longitude
	 *            (in Decimal degrees e.g. -87.64412)
	 */
	Waypoint(double latitude, double longitude, String name, String description)
	{
		this.latitude = latitude;
		this.longitude = longitude;
		this.name = name;
		this.description = description;
	}

	public String getName()
	{
		return name;
	}


	public String getDescription()
	{
		return description;
	}


	public double getLatitude()
	{
		return latitude;
	}

	public double getLongitude()
	{
		return longitude;
	}

	public MinutesCoordinates getLatitudeInMinutesCoords()
	{
		return new MinutesCoordinates(latitude);
	}

	public MinutesCoordinates getLongitudeInMinutesCoords()
	{
		return new MinutesCoordinates(longitude);
	}

	/**
	 * Compares the current waypoint to two others and indicates which one it is
	 * closest to. 0 = all waypoints are the same. -1 means left side is closer,
	 * 1 = right side is closer
	 * 
	 * @param leftSideWaypoint
	 * @param rightSideWaypoint
	 * @return
	 */
	public int isCloserTo(Waypoint leftSideWaypoint, Waypoint rightSideWaypoint)
	{
		Double leftRads = this.distanceFrom(leftSideWaypoint);
		Double rightRads = this.distanceFrom(rightSideWaypoint);

		if (leftRads < rightRads)
			return -1;
		else if (leftRads > rightRads)
			return 1;

		return 0;
	}

	/**
	 * Given a list of waypoints, determines which waypoint one in the list is
	 * closest to this waypoint
	 * 
	 * @param list
	 *            of {@link Waypoint} objects
	 * @return {@link Waypoint} object that is closest to this waypoint
	 */
	public Waypoint isClosestTo(List<Waypoint> waypoints)
	{
		/* This will be the waypoint that is closest */
		Waypoint closestWP = null;
		for (Waypoint currentWP : waypoints)
		{			
			if (closestWP == null)
				closestWP = currentWP;
			else
			{
				/*
				 * If this waypoint is closer to the currentWP than the
				 * closestWP, we replace closestWP with currentWP
				 */
				if (isCloserTo(closestWP, currentWP) == 1)
					closestWP = currentWP;
			}
		}

		return closestWP;
	}

	/**
	 * Returns the distance between this and the argument waypoint Based on the
	 * formulas from {@link http://williams.best.vwh.net/avform.htm}
	 * <p/>
	 * 
	 * It's not not as optimized as I suppose it could be, but I left it this
	 * way so that I could debug it :)
	 * 
	 * @param waypoint
	 * @return distance in radians
	 */
	public Double distanceFrom(Waypoint waypoint)
	{
		MinutesCoordinates thisLatitude = new MinutesCoordinates(this.latitude);
		MinutesCoordinates otherLatitude = new MinutesCoordinates(waypoint.getLatitude());

		MinutesCoordinates thisLongitude = new MinutesCoordinates(this.longitude);
		MinutesCoordinates otherLongitude = new MinutesCoordinates(waypoint.getLongitude());

		Double lat1 = ((double) thisLatitude.getDegrees() + (double) thisLatitude.getMinutes() / 60.0) * Math.PI
						/ 180.0;
		Double lon1 = (Math.abs((double) thisLongitude.getDegrees()) + (double) thisLongitude.getMinutes() / 60.0) * Math.PI
						/ 180.0;

		Double lat2 = ((double) otherLatitude.getDegrees() + (double) otherLatitude.getMinutes() / 60.0) * Math.PI
						/ 180.0;
		Double lon2 = (Math.abs((double) otherLongitude.getDegrees()) + (double) otherLongitude.getMinutes() / 60.0) * Math.PI
						/ 180.0;

		return Math.acos(Math.sin(lat1) * Math.sin(lat2)
							+ Math.cos(lat1)
							* Math.cos(lat2)
							* Math.cos(lon1 - lon2));
	}
	
	/* This is the format Google Maps wants it in */
	public String getFormattedWaypointForGM()
	{
		//return description + "(" + name + ")" + "@" + latitude + "," + longitude;
		return name + "@" + latitude + "," + longitude;
	}

}
